package me.bincker.formwork.web.core.security

import me.bincker.formwork.web.core.annotation.Permission
import me.bincker.formwork.web.core.config.CacheConfiguration
import me.bincker.formwork.web.core.entity.PermissionEntity
import me.bincker.formwork.web.modules.system.repository.IRoleRepository
import me.bincker.formwork.web.utils.SpringUtils
import org.apache.commons.logging.LogFactory
import org.springframework.cache.annotation.Cacheable
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.access.SecurityConfig
import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource
import org.springframework.stereotype.Component
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
import java.lang.reflect.Method
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashSet

/**
 * 当访问方法时获取访问该方法的权限
 */
@Component("customMethodSecurityMetadataSource")
class MethodSecurityMetadataSource(
        val roleRepository: IRoleRepository
): AbstractFallbackMethodSecurityMetadataSource() {

    companion object{
        private val log = LogFactory.getLog(MethodSecurityMetadataSource::class.java)
        private const val SUPER_ADMIN_ROLE = "ROLE_SUPER_ADMIN"
    }

    @Cacheable(CacheConfiguration.CACHE_METHOD_SECURITY, key = "#method")
    override fun findAttributes(method: Method, clazz: Class<*>): MutableCollection<ConfigAttribute> {
        val methodPermission : Permission? = method.getAnnotation(Permission::class.java)
        val clazzPermission :Permission? = method.declaringClass.getAnnotation(Permission::class.java)
        val result = HashSet<ConfigAttribute>()
        if(methodPermission != null){
            result.addAll(
                    roleRepository.findAllByPermission(methodPermission.code).map { role -> SecurityConfig(role.code) }
            )
            result.add(SecurityConfig(SUPER_ADMIN_ROLE))
        }
        if(clazzPermission != null){
            result.addAll(
                    roleRepository.findAllByPermission(clazzPermission.code).map { role -> SecurityConfig(role.code) }
            )
            result.add(SecurityConfig(SUPER_ADMIN_ROLE))
        }
        return result
    }

    @Cacheable(CacheConfiguration.CACHE_METHOD_SECURITY)
    override fun findAttributes(clazz: Class<*>?): Collection<ConfigAttribute> {
        if(clazz == null) return listOf()
        val clazzPermission :Permission? = clazz.getAnnotation(Permission::class.java)
        val result = ArrayList<ConfigAttribute>()
        if(clazzPermission != null){
            result.addAll(roleRepository.findAllByPermission(clazzPermission.code).map { role -> SecurityConfig(role.code) })
            result.add(SecurityConfig(SUPER_ADMIN_ROLE))
        }
        for (method in clazz.declaredMethods) {
            val methodPermission : Permission? = method.getAnnotation(Permission::class.java)
            if(methodPermission != null){
                result.addAll(
                        roleRepository.findAllByPermission(methodPermission.code).map { role -> SecurityConfig(role.code) }
                )
                result.add(SecurityConfig(SUPER_ADMIN_ROLE))
            }
        }
        return result
    }

    /**
     * 获取所有的请求权限
     */
    override fun getAllConfigAttributes() = roleRepository.findAll().map { SecurityConfig(it.code) }

    /**
     * 获取所有权限
     */
    fun getAllPermission() : List<PermissionEntity>{
        val requestMappingHandlerMapping = SpringUtils.getBean(RequestMappingHandlerMapping::class.java)
        return requestMappingHandlerMapping
                .handlerMethods
                .values
                .fold(LinkedList()) {result, handlerMethod->
                    val methodPermission = handlerMethod.method.getAnnotation(Permission::class.java)
                    val classPermission = handlerMethod.method.declaringClass.getAnnotation(Permission::class.java)
                    if(methodPermission != null) result.add(PermissionEntity.of(methodPermission))
                    if(classPermission != null) result.add(PermissionEntity.of(classPermission))
                    return@fold result
                }
    }
}